/***************************** BEGIN LICENSE BLOCK ***************************

 The contents of this file are subject to the Mozilla Public License Version
 1.1 (the "License"); you may not use this file except in compliance with
 the License. You may obtain a copy of the License at
 http://www.mozilla.org/MPL/MPL-1.1.html
 
 Software distributed under the License is distributed on an "AS IS" basis,
 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 for the specific language governing rights and limitations under the License.
 
 The Original Code is the "EO-SPS Framework".
 
 The Initial Developer of the Original Code is Spotimage S.A.
 Portions created by the Initial Developer are Copyright (C) 2007
 the Initial Developer. All Rights Reserved.
 
 Contributor(s): 
    Alexandre Robin <alexandre.robin@spotimage.fr>
 
******************************* END LICENSE BLOCK ***************************/

package com.spotimage.eosps;

import org.vast.xml.DOMHelper;
import org.vast.xml.QName;
import org.vast.cdm.common.DataBlock;
import org.vast.cdm.common.DataComponent;
import org.vast.data.DataArray;
import org.vast.data.DataChoice;
import org.vast.data.DataValue;
import org.vast.data.ParamHelper;
import org.vast.ows.sps.GetFeasibilityRequest;
import org.vast.ows.sps.ReserveRequest;
import org.vast.ows.sps.SubmitRequest;
import org.vast.ows.sps.TaskingRequest;
import org.vast.ows.swe.SWESUtils;
import org.vast.sweCommon.SWEData;
import org.vast.sweCommon.SweConstants;
import org.vast.util.TimeExtent;
import org.w3c.dom.Element;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;


/**
 * <p><b>Title:</b><br/>
 * EO Param Helper
 * </p>
 *
 * <p><b>Description:</b><br/>
 * Helper to get and set tasking parameters that are specific to
 * spaceborne earth observation instruments. More specific helpers
 * are also available for optical and SAR instruments.<br/>
 * This class must be constructed from an existing GetFeasibility,
 * Submit, Update or Reserve request (for instance as obtained using
 * the generic SPS deserializers).
 * 
 * TODO implement unit conversion in all get/set methods dealing with 
 * decimal values
 * 
 * TODO implement multiple survey periods
 * 
 * </p>
 *
 * <p>Copyright (c) 2008, Spot Image</p>
 * @author Alexandre Robin <alexandre.robin@spotimage.fr>
 * @date July, 10th 2008
 * @since 1.0
 */
public class EOParamHelper extends ParamHelper implements EOConstants
{
	protected TaskingRequest request;
	protected DataComponent taskingParams;
	protected SWEData taskingData;
	
	
	/**
	 * Constructor using a request that already contains
	 * properly initialized tasking parameter data (for reading)
	 * @param request Any tasking request object containing tasking data
	 */
	public EOParamHelper(TaskingRequest request)
	{
		this.request = request;
		this.taskingData = request.getParameters();
		loadTaskingParams(this.taskingData);
	}
	
	
	/**
	 * Constructor using a request (without tasking parameter
	 * data) and the report component structure for writing
	 * @param request Any tasking request object receiving tasking data
	 * @param taskingParams tasking data structure
	 */
	public EOParamHelper(TaskingRequest request, DataComponent taskingParams)
	{
		this.request = request;
		this.taskingData = new SWEData();
		this.taskingData.setDataComponents(taskingParams.copy());
		request.setParameters(taskingData);
		loadTaskingParams(taskingData);
	}
	
	
	/**
	 * Sets the feasibility level of a GetFeasibility request
	 * @param level
	 */
	public void setFeasibilityLevel(FeasibilityLevel level)
	{
		if (request instanceof GetFeasibilityRequest)
			addRequestExtension(FEASIBILITY_LEVEL_ELT, level.name());
	}
	
	
	/**
	 * Gets the feasibility level specified in a GetFeasibility request
	 * @return
	 */
	public FeasibilityLevel getFeasibilityLevel()
	{
		if (!(request instanceof GetFeasibilityRequest))
			return null;
		
		String fLevel = getRequestExtension(FEASIBILITY_LEVEL_ELT);
		
		if (fLevel != null)
			return FeasibilityLevel.valueOf(fLevel);
		else
			return FeasibilityLevel.SIMPLE;
	}
	
	
	/**
	 * Sets the reference feasibility ID of a Submit or Reserve request
	 * @param feasibilityID
	 */
	public void setReferenceFeasibilityID(String feasibilityID)
	{
		if (request instanceof SubmitRequest || request instanceof ReserveRequest)
			addRequestExtension(REF_FEASIBILITY_ELT, feasibilityID);
	}
	
	
	/**
	 * Gets the reference feasibility ID specified in a Submit or Reserve request
	 * @return
	 */
	public String getReferenceFeasibilityID()
	{
		if (!(request instanceof SubmitRequest || request instanceof ReserveRequest))
			return null;
		
		return getRequestExtension(REF_FEASIBILITY_ELT);
	}
	
	
	/**
	 * Sets the manual validation mode in a Submit or Reserve request
	 * @param manualValidation
	 */
	public void setManualValidation(boolean manualValidation)
	{
		if (request instanceof SubmitRequest || request instanceof ReserveRequest)
			addRequestExtension(MANUAL_VALIDATION_ELT, manualValidation ? "true" : "false");
	}
	
	
	/**
	 * Gets the feasibility level specified in a GetFeasibility request
	 * @return
	 */
	public boolean getManualValidation()
	{
		if (!(request instanceof SubmitRequest || request instanceof ReserveRequest))
			return false;
		
		String manualValid = getRequestExtension(MANUAL_VALIDATION_ELT);
		
		if (manualValid != null)
			return Boolean.parseBoolean(manualValid);
		else
			return false;
	}
	
	
	/**
	 * Finds priority level field in request parameters structure
	 * @return
	 */
	public DataValue findPriorityLevel()
	{
		DataComponent param = findParameterByDefinition(PRIORITY_URI);
		return (DataValue)param;
	}
	
	
	/**
	 * Gets priority level specified in request
	 * @return
	 */
	public String getPriorityLevel()
	{
		DataValue param = findPriorityLevel();
		if (param == null)
			return null;
		
		return param.getData().getStringValue();
	}
	
	
	/**
	 * Sets priority level in request
	 * @param urgency
	 */
	public void setPriorityLevel(String priority)
	{
		DataValue param = findPriorityLevel();
		if (param != null)
			param.getData().setStringValue(priority);
	}
	
	
	// TODO support multiple ROIs within a DataArray
	
	
	/**
	 * Finds circular ROI field in request parameters structure
	 * @return Data component containing circular ROI data
	 */
	public DataComponent findCircleComponent()
	{
		DataComponent roiCircle = null;
		DataComponent roi = findParameterByDefinition(ROI_URI);
		
		// if there is a choice in the param structure
		if (roi != null && roi instanceof DataChoice)
		{
			DataChoice roiChoice = (DataChoice)roi;
			int selected = roiChoice.getSelected();
			
			// if nothing is selected, try to select polygon 
			if (selected < 0)
			{
				int index = -1;
				
				// find the choice item which is a polygon
				do
				{
					// return null if there is no polygon
					index++;					
					if (index > roiChoice.getComponentCount() - 1)
						return null;
					
					roiCircle = roiChoice.getComponent(index);
				}
				while(!roiCircle.getProperty(SweConstants.DEF_URI).equals(CIRCLE_URI));
				
				// selectes the polygon item
				roiChoice.setSelected(index);
			}
			else
			{
				// if there was a choice, see if circle was selected
				DataComponent roiShape = roiChoice.getSelectedComponent();
				if (roiShape.getProperty(SweConstants.DEF_URI).equals(CIRCLE_URI))
					roiCircle = roiShape;
			}			
		}
		else
		{
			// else try to find the circle directly
			roiCircle = findParameterByDefinition(CIRCLE_URI);
		}
		
		return roiCircle;
	}
	
	
	/**
	 * Get circular ROI specified in request
	 * @return Circle object or null if not found in request
	 */
	public Circle getCircleROI()
	{
		DataComponent roiCircle = findCircleComponent();
		if (roiCircle == null)
			return null;
		
		// create circle object from swe common data
		GeometryFactory fac = new GeometryFactory();
		Circle circle = new Circle();
		circle.setRadius(roiCircle.getComponent(1).getData().getDoubleValue());
		double x = roiCircle.getComponent(0).getComponent("Lon").getData().getDoubleValue();
		double y = roiCircle.getComponent(0).getComponent("Lat").getData().getDoubleValue();
		circle.setCenter(fac.createPoint(new Coordinate(x, y)));
		
		return circle;
	}
	
	
	/**
	 * Sets the circular ROI in request
	 * @param center center point of circle
	 * @param radius distance from center in km
	 */
	public void setCircleROI(Point center, double radius)
	{
		DataComponent roiCircle = findCircleComponent();
		if (roiCircle == null)
			return;
		
		// center
		DataComponent centerData = roiCircle.getComponent(0);
		centerData.getComponent("Lon").getData().setDoubleValue(center.getX());
		centerData.getComponent("Lat").getData().setDoubleValue(center.getY());
		
		// distance
		DataComponent radiusData = roiCircle.getComponent(1);
		radiusData.getData().setDoubleValue(radius);
	}
	
	
	/**
	 * Finds polygon ROI field in request parameters structure
	 * @return Data component containing polygon ROI data
	 */
	public DataComponent findPolygonComponent()
	{
		DataComponent roiPoly = null;
		DataComponent roi = findParameterByDefinition(ROI_URI);
		
		// if there is a choice in the param structure
		if (roi != null && roi instanceof DataChoice)
		{
			DataChoice roiChoice = (DataChoice)roi;
			int selected = roiChoice.getSelected();
			
			// if nothing is selected, try to select polygon 
			if (selected < 0)
			{
				int index = -1;
				
				// find the choice item which is a polygon
				do
				{
					// return null if there is no polygon
					index++;					
					if (index > roiChoice.getComponentCount() - 1)
						return null;
					
					roiPoly = roiChoice.getComponent(index);
				}
				while(!roiPoly.getProperty(SweConstants.DEF_URI).equals(POLYGON_URI));				
				
				// selectes the polygon item
				roiChoice.setSelected(index);
			}
			else
			{
				// if there was a selection, check if polygon was selected
				DataComponent roiShape = roiChoice.getSelectedComponent();
				if (roiShape.getProperty(SweConstants.DEF_URI).equals(POLYGON_URI))
					roiPoly = roiShape;
			}
		}
		
		// if there is no choice of ROI
		else
		{
			// try to find the polygon directly
			roiPoly = findParameterByDefinition(POLYGON_URI);
		}
		
		return roiPoly;
	}
	
	
	/**
	 * Gets the polygonal ROI specified in request 
	 * @return
	 */
	public Polygon getPolygonROI()
	{
		DataComponent roiPoly = findPolygonComponent();
		if (roiPoly == null)
			return null;

		// create polygon object from swe common data
		DataArray exterior = (DataArray)roiPoly.getComponent(0);
		int numPoint = exterior.getComponentCount();
		
		// read all points
		GeometryFactory fac = new GeometryFactory();
		Coordinate[] coordinates = new Coordinate[numPoint];
		for (int p=0; p<numPoint; p++)
		{
			Coordinate coords = new Coordinate();
			DataComponent point = exterior.getComponent(p);
			coords.x = point.getComponent("Lon").getData().getDoubleValue();
			coords.y = point.getComponent("Lat").getData().getDoubleValue();
			coordinates[p] = coords;
		}
		
		// TODO read polygon holes if any
		
		LinearRing outer = fac.createLinearRing(coordinates);
		Polygon poly = fac.createPolygon(outer, null);
		
		return poly;
	}
	
	
	/**
	 * Sets the polygonal ROI in request
	 * @param poly JTS polygon object describing the ROI
	 */
	public void setPolygonROI(Polygon poly)
	{
		DataComponent roiPoly = findPolygonComponent();
		if (roiPoly == null)
			return;
		
		DataArray exterior = (DataArray)roiPoly.getComponent(0);
		int numPoint = poly.getNumPoints();
		exterior.updateSize(numPoint);
		
		for (int p=0; p<numPoint; p++)
		{
			Coordinate coords = poly.getExteriorRing().getCoordinateN(p);
			DataComponent point = exterior.getComponent(p);
			point.getComponent("Lon").getData().setDoubleValue(coords.x);
			point.getComponent("Lat").getData().setDoubleValue(coords.y);
		}
		
		// TODO support polygon holes if allowed in parameter structure
	}
	
	
	/**
	 * Check if type of time of interest is supported
	 * @param type
	 */
	public boolean isTimeOfInterestTypeSupported(ToiType type)
	{
		if (selectTimeOfInterestType(type))
			return true;

		else if (type == ToiType.PERIOD && findParameterByDefinition(SURVEY_PERIOD_URI) != null)
			return true;
			
		else if (type == ToiType.SERIES && findParameterByDefinition(TIME_PERIODICITY_URI) != null)
			return true;
		
		return false;
	}
	
	
	/**
	 * Select type of time of interest
	 * @param type
	 */
	public boolean selectTimeOfInterestType(ToiType type)
	{
		DataComponent toi = findParameterByDefinition(TOI_URI);
		
		if (toi != null && toi instanceof DataChoice)
		{
			DataChoice choice = (DataChoice)toi;
			
			DataComponent periodParams = findParameterByDefinition(SURVEY_PERIOD_URI, choice);
			DataComponent seriesParams = findParameterByDefinition(TIME_PERIODICITY_URI, choice).getParent();
			
			// find the right choice item
			for (int i=0; i<choice.getComponentCount(); i++)
			{
				DataComponent choiceItem = choice.getComponent(i);
				
				if (type == ToiType.PERIOD && choiceItem == periodParams)
				{
					choice.setSelected(i);
					return true;
				}
				
				else if (type == ToiType.SERIES && choiceItem == seriesParams)
				{
					choice.setSelected(i);
					return true;
				}
			}
		}
		
		return false;
	}
	
	
	/**
	 * Retrieve selected type of time of interest
	 * @return
	 */
	public ToiType getTimeOfInterestType()
	{
		DataComponent toi = findParameterByDefinition(TOI_URI);
		
		if (toi != null && toi instanceof DataChoice)
		{
			DataChoice choice = (DataChoice)toi;

			DataComponent periodParams = findParameterByDefinition(SURVEY_PERIOD_URI, choice);
			DataComponent seriesParams = findParameterByDefinition(TIME_PERIODICITY_URI, choice).getParent();
						
			if (choice.getSelectedComponent() == seriesParams)
				return ToiType.SERIES;
			else if (choice.getSelectedComponent() == periodParams)
				return ToiType.PERIOD;
			else
				return ToiType.UNKNOWN;
		}	
		else
		{
			if (findParameterByDefinition(TIME_PERIODICITY_URI) != null)
				return ToiType.SERIES;
			else if (findParameterByDefinition(SURVEY_PERIOD_URI) != null)
				return ToiType.PERIOD;
			else
				return ToiType.UNKNOWN;
		}
	}
	
	
	/**
	 * Finds survey end field in request parameters structure
	 * @return
	 */
	public DataComponent findSurveyPeriod()
	{
		DataComponent toi = findParameterByDefinition(TOI_URI);
		DataComponent surveyPeriod;
		
		// if there is a choice in the param structure
		if (toi != null && toi instanceof DataChoice)
		{
			toi = ((DataChoice)toi).getSelectedComponent();
			surveyPeriod = findParameterByDefinition(SURVEY_PERIOD_URI, toi);
		}
		else
		{
			// try to find temporal series record directly
			surveyPeriod = findParameterByDefinition(SURVEY_PERIOD_URI);
		}
		
		return surveyPeriod;
	}
	
	
	/**
	 * Gets the survey start date specified in request
	 * @return unix time (seconds past January 1st, 1970) of survey start
	 */
	public double getSurveyStartDate()
	{
		DataComponent param = findSurveyPeriod();
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(0);
	}
	
	
	/**
	 * Gets the survey end date specified in request
	 * @return unix time (seconds past January 1st, 1970) of survey end
	 */
	public double getSurveyEndDate()
	{
		DataComponent param = findSurveyPeriod();
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(1);
	}
	
	
	/**
	 * Gets the survey period specified in request
	 * @return extent in julian/unix time (seconds past January 1st, 1970) of survey period
	 */
	public TimeExtent getSurveyPeriod()
	{
		TimeExtent surveyPeriod = new TimeExtent();
		surveyPeriod.setStartTime(getSurveyStartDate());
		surveyPeriod.setStopTime(getSurveyEndDate());
		return surveyPeriod;
	}
	
	
	/**
	 * Sets the survey period in request
	 * @param begin unix time of survey start
	 * @param end unix time of survey end
	 */
	public void setSurveyPeriod(double begin, double end)
	{
		DataComponent param = findSurveyPeriod();
		if (param == null)
			return;
		
		param.getData().setDoubleValue(0, begin);
		param.getData().setDoubleValue(1, end);
	}
	
	
	/**
	 * Finds time series occurences field in request parameters structure 
	 * @return
	 */
	public DataValue findTimeSeriesOccurences()
	{
		DataComponent param = findParameterByDefinition(TIME_OCCURENCES_URI);
		return (DataValue)param;
	}
	
	
	/**
	 * Gets number of time series occurences specified in request
	 * @return 
	 */
	public int getTimeSeriesOccurences()
	{
		DataValue param = findTimeSeriesOccurences();
		if (param == null)
			return -1;
		
		return param.getData().getIntValue();
	}
	
	
	/**
	 * Sets time series occurences in request
	 * @return 
	 */
	public void setTimeSeriesOccurences(int occurences)
	{
		assert (occurences > 0);
		
		DataValue param = findTimeSeriesOccurences();
		if (param != null)
			param.getData().setIntValue(occurences);
	}
	
	
	/**
	 * Finds time series periodicity field in request parameters structure 
	 * @return
	 */
	public DataComponent findTimeSeriesPeriodicity()
	{
		DataComponent param = findParameterByDefinition(TIME_PERIODICITY_URI);
		return param;
	}
	
	/**
	 * Gets minimum time series periodicity specified in request
	 * @return min periodicity in days
	 */
	public double getMinTimeSeriesPeriodicity()
	{
		DataComponent param = findTimeSeriesPeriodicity();
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(0);
	}
	
	
	/**
	 * Gets maximum time series periodicity specified in request
	 * @return max periodicity in days
	 */
	public double getMaxTimeSeriesPeriodicity()
	{
		DataComponent param = findTimeSeriesPeriodicity();
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(1);
	}
	
	
	/**
	 * Sets min and max time series periodicity in request
	 * @param min in days
	 * @param max in days
	 */
	public void setTimeSeriesPeriodicityRange(double min, double max)
	{
		DataComponent param = findTimeSeriesPeriodicity();
		if (param == null)
			return;
		
		param.getData().setDoubleValue(0, min);
		param.getData().setDoubleValue(1, max);
	}
	
	
	/**
	 * Finds time series latest start field in request parameters structure 
	 * @return 
	 */
	public DataValue findTimeSeriesLatestStart()
	{
		DataComponent param = findParameterByDefinition(TIME_LATEST_START_URI);
		return (DataValue)param;
	}
	
	
	/**
	 * Gets time series latest start specified in request
	 * @return julian time of latest start
	 */
	public double getTimeSeriesLatestStart()
	{
		DataValue param = findTimeSeriesLatestStart();
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue();
	}
	
	
	/**
	 * Sets time series latest start in request
	 * @param latestStart
	 */
	public void setTimeSeriesLatestStart(double latestStart)
	{
		DataComponent param = findTimeSeriesLatestStart();
		if (param != null)
			param.getData().setDoubleValue(latestStart);
	}
	
	
	/**
	 * Finds coverage type field in request parameters structure 
	 * @return
	 */
	public DataValue findCoverageType()
	{
		DataComponent param = findParameterByDefinition(COVERAGE_TYPE_URI);
		return (DataValue)param;
	}
		
	
	/**
	 * Check if acquisition type is supported
	 * @param type
	 */
	public boolean isAcquisitionTypeSupported(AcquisitionType type)
	{
		if (selectAcquisitionType(type))
			return true;

		else if (type == AcquisitionType.MONO && findParameterByDefinition(MONO_ACQ_URI) != null)
			return true;
		
		else if (type == AcquisitionType.STEREO && findParameterByDefinition(STEREO_ACQ_URI) != null)
			return true;
		
		return false;
	}
	
	
	/**
	 * Select the acquisition type
	 * @param type
	 * @return
	 */
	public boolean selectAcquisitionType(AcquisitionType type)
	{
		DataComponent acqType = findParameterByDefinition(ACQ_TYPE_URI);
		
		if (acqType != null && acqType instanceof DataChoice)
		{
			DataChoice choice = (DataChoice)acqType;
			
			DataComponent monoParams = findParameterByDefinition(MONO_ACQ_URI, choice);
			DataComponent stereoParams = findParameterByDefinition(STEREO_ACQ_URI, choice);
			
			// find the right choice item
			for (int i=0; i<choice.getComponentCount(); i++)
			{
				DataComponent choiceItem = choice.getComponent(i);
				
				if (type == AcquisitionType.MONO && choiceItem == monoParams)
				{
					choice.setSelected(i);
					return true;
				}
				
				else if (type == AcquisitionType.STEREO && choiceItem == stereoParams)
				{
					choice.setSelected(i);
					return true;
				}
			}
		}
		
		return false;
	}
	
	
	/**
	 * Retrieve selected acquisition type
	 * @return
	 */
	public AcquisitionType getAcquisitionType()
	{
		DataComponent acqType = findParameterByDefinition(ACQ_TYPE_URI);
		
		if (acqType != null && acqType instanceof DataChoice)
		{
			DataChoice choice = (DataChoice)acqType;
			
			DataComponent monoParams = findParameterByDefinition(MONO_ACQ_URI, choice);
			DataComponent stereoParams = findParameterByDefinition(STEREO_ACQ_URI, choice);
			
			if (choice.getSelectedComponent() == monoParams)
				return AcquisitionType.MONO;
			else if (choice.getSelectedComponent() == stereoParams)
				return AcquisitionType.STEREO;
			else
				return AcquisitionType.UNKNOWN;
		}	
		else
		{
			if (findParameterByDefinition(MONO_ACQ_URI) != null)
				return AcquisitionType.MONO;
			else if (findParameterByDefinition(STEREO_ACQ_URI) != null)
				return AcquisitionType.STEREO;
			else
				return AcquisitionType.UNKNOWN;
		}
	}
	
	
	/**
	 * Gets coverage type specified in request
	 * @return
	 */
	public CoverageType getCoverageType()
	{
		DataValue param = findCoverageType();
		if (param == null)
			return null;
		
		return CoverageType.valueOf(param.getData().getStringValue());
	}
	
	/**
	 * Sets coverage type in request
	 * @param type
	 */
	public void setCoverageType(CoverageType coverageType)
	{
		DataValue param = findCoverageType();
		if (param != null)
			param.getData().setStringValue(coverageType.name());
	}
	
	
	public DataComponent findAcquisitionAngleGroup(int stereoViewIndex)
	{
		DataComponent param;
		
		if (stereoViewIndex > 0)
			param = findParameterByName("AcquisitionAngle" + stereoViewIndex);
		else
			param = findParameterByName("AcquisitionAngle");
		
		return param;
	}
	
	
	/**
	 * Finds azimuth incidence field in request parameters structure 
	 * @return
	 */
	public DataComponent findAzimuthIncidence(int stereoViewIndex)
	{
		DataComponent param = findAcquisitionAngleGroup(stereoViewIndex);
		if (param == null)
			return null;
		
		param = findParameterByDefinition(AZIMUTH_URI, param);
		return param;
	}
	
	
	public double getMinAzimuthIncidence(int stereoViewIndex)
	{
		DataComponent param = findAzimuthIncidence(stereoViewIndex);
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(0);
	}
	
	
	public double getMinAzimuthIncidence()
	{
		return getMinAzimuthIncidence(-1);
	}
	
	
	public double getMaxAzimuthIncidence(int stereoViewIndex)
	{
		DataComponent param = findAzimuthIncidence(stereoViewIndex);
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(1);
	}
	
	
	public double getMaxAzimuthIncidence()
	{
		return getMaxAzimuthIncidence(-1);
	}
	
	
	public void setAzimuthIncidenceRange(int stereoViewIndex, double min, double max)
	{
		DataComponent param = findAzimuthIncidence(stereoViewIndex);
		if (param == null)
			return;
		
		param.getData().setDoubleValue(0, min);
		param.getData().setDoubleValue(1, max);
	}
	
	
	public void setAzimuthIncidenceRange(double min, double max)
	{
		setAzimuthIncidenceRange(-1, min, max);
	}
	

	/**
	 * Finds elevation incidence field in request parameters structure 
	 * @return
	 */
	public DataComponent findElevationIncidence(int stereoViewIndex)
	{
		DataComponent param = findAcquisitionAngleGroup(stereoViewIndex);
		if (param == null)
			return null;
		
		param = findParameterByDefinition(ELEVATION_URI, param);
		return param;
	}
	
	
	public double getMinElevationIncidence(int stereoViewIndex)
	{
		DataComponent param = findElevationIncidence(stereoViewIndex);
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(0);
	}
	
	
	public double getMinElevationIncidence()
	{
		return getMinElevationIncidence(-1);
	}
	
	
	public double getMaxElevationIncidence(int stereoViewIndex)
	{
		DataComponent param = findElevationIncidence(stereoViewIndex);
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(1);
	}
	
	
	public double getMaxElevationIncidence()
	{
		return getMaxElevationIncidence(-1);
	}
	
	
	public void setElevationIncidenceRange(int stereoViewIndex, double min, double max)
	{
		DataComponent param = findElevationIncidence(stereoViewIndex);
		if (param == null)
			return;
		
		param.getData().setDoubleValue(0, min);
		param.getData().setDoubleValue(1, max);
	}
	
	
	public void setElevationIncidenceRange(double min, double max)
	{
		setElevationIncidenceRange(-1, min, max);
	}
	
	
	/**
	 * Finds across-track pointing field in request parameters structure 
	 * @return
	 */
	public DataComponent findAcrossTrackPointing(int stereoViewIndex)
	{
		DataComponent param = findAcquisitionAngleGroup(stereoViewIndex);
		if (param == null)
			return null;
		
		param = findParameterByDefinition(ACROSS_TRACK_URI, param);
		return param;
	}
	
	
	public double getMinAcrossTrackPointing(int stereoViewIndex)
	{
		DataComponent param = findAcrossTrackPointing(stereoViewIndex);
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(0);
	}
	
	
	public double getMinAcrossTrackPointing()
	{
		return getMinAcrossTrackPointing(-1);
	}
	
	
	public double getMaxAcrossTrackPointing(int stereoViewIndex)
	{
		DataComponent param = findAcrossTrackPointing(stereoViewIndex);
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(1);
	}
	
	
	public double getMaxAcrossTrackPointing()
	{
		return getMaxAcrossTrackPointing(-1);
	}
	
	
	public void setAcrossTrackPointingRange(int stereoViewIndex, double min, double max)
	{
		DataComponent param = findAcrossTrackPointing(stereoViewIndex);
		if (param == null)
			return;
		
		param.getData().setDoubleValue(0, min);
		param.getData().setDoubleValue(1, max);
	}
	
	
	public void setAcrossTrackPointingRange(double min, double max)
	{
		setAcrossTrackPointingRange(-1, min, max);
	}
	
	
	/**
	 * Finds across-track pointing field in request parameters structure
	 * @return
	 */
	public DataComponent findAlongTrackPointing(int stereoViewIndex)
	{
		DataComponent param = findAcquisitionAngleGroup(stereoViewIndex);
		if (param == null)
			return null;
		
		param = findParameterByDefinition(ALONG_TRACK_URI, param);
		return param;
	}
	
	
	public double getMinAlongTrackPointing(int stereoViewIndex)
	{
		DataComponent param = findAlongTrackPointing(stereoViewIndex);
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(0);
	}
	
	
	public double getMinAlongTrackPointing()
	{
		return getMinAlongTrackPointing(-1);
	}
	
	
	public double getMaxAlongTrackPointing(int stereoViewIndex)
	{
		DataComponent param = findAlongTrackPointing(stereoViewIndex);
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(1);
	}
	
	
	public double getMaxAlongTrackPointing()
	{
		return getMaxAlongTrackPointing(-1);
	}
	
	
	public void setAlongTrackPointingRange(int stereoViewIndex, double min, double max)
	{
		DataComponent param = findAlongTrackPointing(stereoViewIndex);
		if (param == null)
			return;
		
		param.getData().setDoubleValue(0, min);
		param.getData().setDoubleValue(1, max);
	}
	
	
	public void setAlongTrackPointingRange(double min, double max)
	{
		setAlongTrackPointingRange(-1, min, max);
	}
	
	
	/**
	 * Finds BHRatio field in request parameters structure 
	 * @return
	 */
	public DataValue findBHRatio()
	{
		DataComponent param = findParameterByDefinition(BHRATIO_URI);
		return (DataValue)param;
	}
	
	
	public double getBHRatio()
	{
		DataValue param = findBHRatio();
		if (param == null)
			return -1;
		
		return param.getData().getDoubleValue();
	}
	
	
	public void setBHRatio(double bhRatio)
	{
		DataValue param = findBHRatio();
		if (param != null)
			param.getData().setDoubleValue(bhRatio);
	}
	
	
	/**
	 * Finds MaxCoupleDelay field in request parameters structure 
	 * @return
	 */
	public DataValue findMaxCoupleDelay()
	{
		DataComponent param = findParameterByDefinition(MAX_COUPLE_DELAY_URI);
		return (DataValue)param;
	}
	
	
	public double getMaxCoupleDelay()
	{
		DataValue param = findMaxCoupleDelay();
		if (param == null)
			return -1;
		
		return param.getData().getDoubleValue();
	}
	
	
	public void setMaxCoupleDelay(double maxCoupleDelay)
	{
		DataValue param = findMaxCoupleDelay();
		if (param != null)
			param.getData().setDoubleValue(maxCoupleDelay);
	}
	
	
	/**
	 * Finds ground resolution field in request parameters structure 
	 * @return
	 */
	public DataComponent findGroundResolution()
	{
		DataComponent param = findParameterByDefinition(GROUND_RES_URI);
		return param;
	}
	
	
	public double getMinGroundResolution()
	{
		DataComponent param = findGroundResolution();
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(0);
	}
	
	
	public double getMaxGroundResolution()
	{
		DataComponent param = findGroundResolution();
		if (param == null)
			return Double.NaN;
		
		return param.getData().getDoubleValue(1);
	}
	
	
	public void setGroundResolutionRange(double min, double max)
	{
		DataComponent param = findGroundResolution();
		if (param == null)
			return;
		
		param.getData().setDoubleValue(0, min);
		param.getData().setDoubleValue(1, max);
	}
	
	
	/**
	 * Finds ground resolution field in request parameters structure 
	 * @return
	 */
	public DataValue findInstrumentMode()
	{
		DataComponent param = findParameterByDefinition(INSTRUMENT_MODE_URI);
		return (DataValue)param;
	}
	
	
	public String getInstrumentMode()
	{
		DataValue param = findInstrumentMode();
		if (param == null)
			return null;
		
		return param.getData().getStringValue();
	}
	
	
	public void setInstrumentMode(String mode)
	{
		DataValue param = findInstrumentMode();
		if (param != null)
			param.getData().setStringValue(mode);
	}
	
	
	/**
	 * Finds fusion accepted field in request parameters structure 
	 * @return
	 */
	public DataValue findFusionAccepted()
	{
		DataComponent param = findParameterByDefinition(FUSION_URI);
		return (DataValue)param;
	}
	
	
	public boolean isFusionAccepted()
	{
		DataValue param = findFusionAccepted();
		if (param != null)
			return param.getData().getBooleanValue();
		else
			return true;
	}
	
	
	public void setFusionAccepted(boolean fusionOk)
	{
		DataValue param = findFusionAccepted();
		if (param != null)
			param.getData().setBooleanValue(fusionOk);
	}
	
	
	/**
	 * Finds a parameter with the given name anywhere in the
	 * tasking parameters structure
	 * @param name
	 * @return
	 */
	public DataComponent findParameterByName(String name)
	{
		return findParameterByName(name, taskingParams);
	}
	
	
	/**
	 * Finds a parameter with the given definition URI anywhere
	 * in the tasking parameters structure.
	 * @param defUri
	 * @return
	 */
	public DataComponent findParameterByDefinition(String defUri)
	{
		return findParameterByDefinition(defUri, taskingParams);
	}	
	
	
	/**
	 * Initializes the block of data when this helper is used
	 * for setting values of a new request
	 * @param taskingData
	 * @param useForWriting
	 */
	protected void loadTaskingParams(SWEData taskingData)
	{
		taskingParams = taskingData.getDataComponents();
		
		// prepare request for writing!
		if (taskingData.getDataList().getComponentCount() == 0)
		{			
			taskingParams.assignNewDataBlock();
			taskingData.getDataList().addData(taskingParams.getData());
		}
		else
		{
			DataBlock paramData = taskingData.getDataList().getComponent(0).getData();
			taskingParams.setData(paramData);
		}
	}
	
	
	protected void addRequestExtension(String eltName, String value)
	{
		DOMHelper dom = new DOMHelper("root");
		dom.addUserPrefix(EOT_PREFIX, EOT_NAMESPACE);
		Element extElt =  dom.setElementValue(eltName, value);
		QName qname = new QName(extElt.getNamespaceURI(), extElt.getLocalName());
		request.getExtensions().put(qname, extElt);
	}
	
	
	public String getRequestExtension(String eltName)
	{
		Element extElt = (Element)SWESUtils.findExtension(EOT_NAMESPACE, eltName, request.getExtensions());
		
		if (extElt != null && extElt.getTextContent() != null)
			return extElt.getTextContent().trim();
		else
			return null;
	}
}
